package org.springframework.core.annotation;

import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.Getter;
import lombok.SneakyThrows;
import org.springframework.core.annotation.AnnotationTypeMapping.AttributeMethod;
import org.springframework.util.AliasUtils.AliasChain;
import org.springframework.util.AliasUtils.AliasChains;
import org.springframework.util.MethodKey;

import java.lang.annotation.Annotation;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Getter
public class AnnotationTypeMappings
        extends AbstractCollection<AnnotationTypeMappings.MappingContext> {
    // 代解析对象跟注解
    private final Annotation rootAnnotation;
    private final AnnotationFilter filter;
    // 别名分组
    private final AliasChains<MethodKey> allAliasChains;
    // 注解类型映射上下文
    private final List<MappingContext> mappingContextList;

    @SneakyThrows
    private AnnotationTypeMappings(Annotation rootAnnotation,
                                   AnnotationFilter filter) {
        this.rootAnnotation = rootAnnotation;
        this.filter = filter;
        this.mappingContextList = new ArrayList<>();
        this.allAliasChains = new AliasChains<>();

        flatAllMappingConextList(rootAnnotation);
        resolveAllAliasGroups();
    }

    /**
     * 归集所有元注解上的所有别名
     */
    private void resolveAllAliasGroups() {
        Map<MethodKey, AttributeMethod> allAttributeMethodMap = mappingContextList.stream()
                .map(MappingContext::getMapping)
                .flatMap(mapping -> mapping.getAttributeMethods().stream())
                .collect(Collectors.toMap(AttributeMethod::getMethodKey, Function.identity()));

        Map<Class<?>, MappingContext> allMappingContextMap = mappingContextList.stream()
                .collect(Collectors.toMap(mappingContext -> mappingContext.getMapping().getAnnotationType(), Function.identity()));

        for (MappingContext mappingContext : Lists.reverse(mappingContextList)) {
            allAliasChains.merge(mappingContext.getMapping().getAliasChains());
        }

        setAliasGroupValueSupplier(allAliasChains, allMappingContextMap, allAttributeMethodMap);
    }

    private void setAliasGroupValueSupplier(AliasChains<MethodKey> allAliasChains,
                                            Map<Class<?>, MappingContext> allMappingContextMap,
                                            Map<MethodKey, AttributeMethod> allAttributeMethodMap) {
        for (AliasChain<MethodKey> aliasChain : allAliasChains) {
            Set<MethodKey> methodKeySet = aliasChain.getElements();
            TreeMap<Integer, List<MethodKey>> treeMap = methodKeySet.stream().collect(Collectors.groupingBy(this::getDistance, TreeMap::new, Collectors.toList()));
            Map.Entry<Integer, List<MethodKey>> firstEntry = treeMap.firstEntry();
            List<MethodKey> keys = firstEntry.getValue();
            Object usingValue = null, defaultValue = null;
            for (MethodKey key : keys) {
                MappingContext mappingContext = allMappingContextMap.get(key.getDeclaringClass());
                Annotation annotation = mappingContext.getAnnotation();
                AttributeMethod attributeMethod = allAttributeMethodMap.get(key);
                Object value = attributeMethod.getValue(annotation);
                defaultValue = attributeMethod.getDefaultValue();
                if (!Objects.equals(value, defaultValue)) {
                    usingValue = value;
                }
            }

            Object v = Optional.ofNullable(usingValue).orElse(defaultValue);
            aliasChain.setValueSupplier(() -> {
                return v;
            });
        }
    }

    private int getDistance(MethodKey methodKey) {
        return this.mappingContextList.stream().filter(mappingContext -> Objects.equals(mappingContext.getAnnotationType(), methodKey.getDeclaringClass())).findFirst().get().getDistance();
    }

    private AnnotationTypeMappings(Annotation annotation) {
        this(annotation, AnnotationFilter.PLAIN);
    }

    static AnnotationTypeMappings of(Annotation annotation) {
        return new AnnotationTypeMappings(annotation);
    }

    /**
     * 平摊所有注解类型上下文
     * @param rootAnnotation
     * @return
     */
    private List<MappingContext> flatAllMappingConextList(Annotation rootAnnotation) {
        Deque<MappingContext> queue = new ArrayDeque<>();
        queue.add(new MappingContext(rootAnnotation, null));

        while (!queue.isEmpty()) {
            MappingContext mappingContext = queue.removeFirst();
            // 同一注解类型只处理最临近的一个
            if (isAlreadyMapped(mappingContext.getAnnotation())) {
                continue;
            }

            mappingContextList.add(mappingContext);

            // 处理元注解
            Annotation[] annotations = AnnotationUtils.getDeclaredAnnotations(mappingContext.getAnnotationType());
            for (Annotation annotation : annotations) {
                queue.add(new MappingContext(annotation, mappingContext));
            }
        }

        return mappingContextList;
    }

    private boolean isAlreadyMapped(Annotation metaAnnotation) {
        for (MappingContext mappingContext : this.mappingContextList) {
            if (Objects.equals(mappingContext.getAnnotation().annotationType(), metaAnnotation.annotationType())) {
                return true;
            }
        }

        return false;
    }

    @Override
    public Iterator<MappingContext> iterator() {
        return mappingContextList.stream().iterator();
    }

    @Override
    public int size() {
        return mappingContextList.size();
    }

    @Data
    class MappingContext {
        private final Annotation annotation;
        private final Class<? extends Annotation> annotationType;
        private final AnnotationTypeMapping mapping;
        private final MappingContext sourceMappingContext;
        private final int distance;
        private final AnnotationTypeMappings mappings;

        public MappingContext(Annotation annotation,
                              MappingContext sourceMappingContext) {
            this.annotation = annotation;
            this.annotationType = annotation.annotationType();
            this.mapping = AnnotationTypeMapping.of(annotation.annotationType());
            this.sourceMappingContext = sourceMappingContext;
            this.distance = sourceMappingContext == null ? 0 : sourceMappingContext.getDistance() + 1;
            this.mappings = AnnotationTypeMappings.this;
        }

        public boolean isType(Object object) {
            if (object instanceof Class<?> annotationType) {
                return isType(annotationType);
            }

            throw new RuntimeException();
        }

        public boolean isType(Class<?> annotationType) {
            return this.annotationType == annotationType;
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this).add("annotation", annotation).add("distance", distance).toString();
        }
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("rootAnnotation", rootAnnotation.annotationType().getSimpleName())
                .add("allAliasGroups", allAliasChains.size())
                .add("mappingContextList", mappingContextList.size())
                .toString();
    }
}
